package com.sk.rpcAssist.policy;


import com.sk.rpcAssist.annotation.RpcConsumer;
import com.sk.rpcAssist.annotation.RpcProvide;
import com.sk.rpcAssist.provideAssist.ProviderAssistStrategyContent;
import com.sk.rpcAssist.util.JarUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 用于Spring动态注入自定义接口
 *
 * @author 孙文斌
 */

@Data
@Slf4j
@Component
public class ServiceBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor, ResourceLoaderAware, ApplicationContextAware {

    public static final ConcurrentHashMap<String,Object> provideMap = new ConcurrentHashMap();
    public static final Set<Class<?>> consumerBeanClazzs =  Collections.synchronizedSet(new HashSet<>());


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        try {
            //获取目标class全路径
            List<String> classPathByJar = JarUtils.getClassPathByJar(this);

            List<Class> classes = JarUtils.loadClassByLoader(this.getClass().getClassLoader());
            //获取所有待动态注入bean的消费者接口
            getConsumerInterface(classes);

            //处理服务提供者
            processProvide();

            //为消费者创建代理(防止自动创建的代理类和上面判定服务提供者的逻辑混淆)
            generateProxyConsumer(registry);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void processProvide() {
        //获取服务提供者
        Map<String, Object> annMap = this.applicationContext.getBeansWithAnnotation(RpcProvide.class);
        //进行注册到集合中
        Class<? extends Object> clazz = null;
        for (Map.Entry<String, Object> entry : annMap.entrySet()) {
            Object object = entry.getValue();//获取到实例对象的class信息
            Class<?>[] interfaces = entry.getValue().getClass().getInterfaces();
            //防止用户在类上加RpcProvide注解
            if (interfaces != null) {
                for (Class<?> anInterface : interfaces) {
                    RpcProvide annotation = anInterface.getAnnotation(RpcProvide.class);
                    if (annotation != null) {
                        //检查二义性(每个带有RpcProvide注解的接口只能有一个实现类，不然通过接口全路径调用的时候，会出现二义性)
                        if (this.provideMap.get(anInterface.getName()) != null) {
                            throw new RuntimeException("Expected only one implementation class per provider interface, but found multiple,interfaces info :" + anInterface.getName());
                        }
                        this.provideMap.put(anInterface.getName(), entry.getValue());
                    }
                }
            }
        }
        //加载服务提供者策略
        if(!CollectionUtils.isEmpty(this.provideMap)){
            ProviderAssistStrategyContent.doStart();
        }
    }

    private void generateProxyConsumer(BeanDefinitionRegistry registry) {
        if(consumerBeanClazzs.isEmpty()){
            return;
        }
        for (Class beanClazz : consumerBeanClazzs) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
            GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);
            definition.setBeanClass(ServiceFactory.class);
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
        }
    }

    private void getConsumerInterface(List<Class> classes) {
        try {
            for (Class clazz : classes) {
//                Class<?> aClass = Class.forName(classPath);
                //判断consumer
                if (clazz.isInterface()) {
                    //判断该接口是否有特定的注解
                    Annotation annotation = clazz.getAnnotation(RpcConsumer.class);
                    if (annotation != null) {
                        //判断是不是consumer端
                        Map<String, ?> beanMap = this.applicationContext.getBeansOfType(clazz);
                        if(beanMap.isEmpty()){
                            this.consumerBeanClazzs.add(clazz);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    private MetadataReaderFactory metadataReaderFactory;

    /**
     * 根据包路径获取包及子包下的所有类
     *
     * @param basePackage basePackage
     * @return Set<Class   <   ?>> Set<Class<?>>
     */
    private Set<Class<?>> scannerPackages(String basePackage) {
        Set<Class<?>> set = new LinkedHashSet<>();
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN;
        try {
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    String className = metadataReader.getClassMetadata().getClassName();
                    Class<?> clazz;
                    try {
                        clazz = Class.forName(className);
                        //判断是否是接口
//                        if (clazz.isInterface()) {
//                            //判断该接口是否有特定的注解
//                            RpcAnno anno = clazz.getAnnotation(RpcAnno.class);
//                            if (anno != null) {
//                                set.add(clazz);
//                            }
//                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return set;
    }

    protected String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(this.getEnvironment().resolveRequiredPlaceholders(basePackage));
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    private ResourcePatternResolver resourcePatternResolver;

    public static ApplicationContext applicationContext;

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    }

    /*
       获取applicationContext的三种方法之一（ApplicationContextAware通过它Spring容器会自动把上下文环境对象调用ApplicationContextAware接口中的setApplicationContext方法）
    * */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /*
     获取jvm属性和系统属性
    * */
    private Environment getEnvironment() {
        return applicationContext.getEnvironment();
    }
}